package cn.momo.mobile.view.concact;

import java.util.Map;

import cn.momo.mobile.bean.Contact;
import cn.momo.mobile.db.ContactHandler;
import cn.momo.mobile.utils.HanziToPinyin;


import android.database.Cursor;
import android.database.DataSetObserver;
import android.util.SparseIntArray;
import android.widget.SectionIndexer;

public class JPSectionIndexer extends DataSetObserver implements SectionIndexer {
	
    protected Cursor mDataCursor;

    protected int mColumnIndex;
	protected CharSequence mAlphabet;
	private int mAlphabetLength;
	private String[] mAlphabetArray;
	private SparseIntArray mAlphaMap;
	private java.text.Collator mCollator;
	
	private Map<String,Contact> mContactsMap;
	
	public JPSectionIndexer(Cursor cursor, int columnIndex, CharSequence alphabet
			, Map<String,Contact> contactsMap){
		
        mDataCursor = cursor;
        mColumnIndex = columnIndex;
        mAlphabet = alphabet;
        mContactsMap = contactsMap;
        mAlphabetLength = alphabet.length();
        mAlphabetArray = new String[mAlphabetLength];
        for (int i = 0; i < mAlphabetLength; i++) {
            mAlphabetArray[i] = Character.toString(mAlphabet.charAt(i));
        }
        mAlphaMap = new SparseIntArray(mAlphabetLength);
        if (cursor != null) {
            cursor.registerDataSetObserver(this);
        }
        mCollator = java.text.Collator.getInstance();
        mCollator.setStrength(java.text.Collator.PRIMARY);
	}

	@Override
	public Object[] getSections() {
		return mAlphabetArray;
	}
	
	public int getSectionIndex(String letter){
		for (int i = 0; i < mAlphabetArray.length; i++) {
			if(mAlphabetArray[i].equals(letter)){
				return i;
			}
		}
		return -1;
	}
	
    public void setCursor(Cursor cursor) {
        if (mDataCursor != null) {
            mDataCursor.unregisterDataSetObserver(this);
        }
        mDataCursor = cursor;
        if (cursor != null) {
            mDataCursor.registerDataSetObserver(this);
        }
        mAlphaMap.clear();
    }
    
    protected int compare(String word, String letter) {
        final String firstLetter;
        if (word.length() == 0) {
            firstLetter = " ";
        } else {
            firstLetter = word.substring(0, 1);
        }

        return mCollator.compare(firstLetter, letter);
    }

	@Override
	public int getPositionForSection(int sectionIndex) {
		final SparseIntArray alphaMap = mAlphaMap;
        final Cursor cursor = mDataCursor;

        if (cursor == null || cursor.isClosed() || mAlphabet == null) {
            return 0;
        }

        // Check bounds
        if (sectionIndex <= 0) {
            return 0;
        }
        if (sectionIndex >= mAlphabetLength) {
            sectionIndex = mAlphabetLength - 1;
        }

        int savedCursorPos = cursor.getPosition();

        int count = cursor.getCount();
        int start = 0;
        int end = count;
        int pos;

        char letter = mAlphabet.charAt(sectionIndex);
        String targetLetter = Character.toString(letter);
        int key = letter;
        // Check map
        if (Integer.MIN_VALUE != (pos = alphaMap.get(key, Integer.MIN_VALUE))) {
            // Is it approximate? Using negative value to indicate that it's
            // an approximation and positive value when it is the accurate
            // position.
            if (pos < 0) {
                pos = -pos;
                end = pos;
            } else {
                // Not approximate, this is the confirmed start of section, return it
                return pos;
            }
        }

        // Do we have the position of the previous section?
        if (sectionIndex > 0) {
            int prevLetter =
                    mAlphabet.charAt(sectionIndex - 1);
            int prevLetterPos = alphaMap.get(prevLetter, Integer.MIN_VALUE);
            if (prevLetterPos != Integer.MIN_VALUE) {
                start = Math.abs(prevLetterPos);
            }
        }

        // Now that we have a possibly optimized start and end, let's binary search

        pos = (end + start) / 2;

        while (pos < end) {
            // Get letter at pos
            cursor.moveToPosition(pos);
//            String curName = cursor.getString(mColumnIndex);
            String curName = null;
            String contactId = cursor.getString(mColumnIndex);
            if(mContactsMap == null || mContactsMap.size() == 0){
            	return 0;
            } else {
            	Contact contact = mContactsMap.get(contactId);
            	if(contact == null) {
					String name = cursor.getString(ContactHandler.DISPLAY_NAME_COLUMN_INDEX);
					String number = cursor.getString(ContactHandler.NUMBER_COLUMN_INDEX);
					contact = new Contact();
					contact.setContactId(contactId);
					contact.setDisplayName(name);
					contact.setPinyin(HanziToPinyin.getInstance().getSortKey(name));
					contact.setMobile(number);
					mContactsMap.put(contactId, contact);
            	} else {
            		curName = contact.getPinyin();
            	}
            }
            
            if (curName == null) {
                if (pos == 0) {
                    break;
                } else {
                    pos--;
                    continue;
                }
            }
            int diff = compare(curName, targetLetter);
            if (diff != 0) {
                // TODO: Commenting out approximation code because it doesn't work for certain
                // lists with custom comparators
                // Enter approximation in hash if a better solution doesn't exist
                // String startingLetter = Character.toString(getFirstLetter(curName));
                // int startingLetterKey = startingLetter.charAt(0);
                // int curPos = alphaMap.get(startingLetterKey, Integer.MIN_VALUE);
                // if (curPos == Integer.MIN_VALUE || Math.abs(curPos) > pos) {
                //     Negative pos indicates that it is an approximation
                //     alphaMap.put(startingLetterKey, -pos);
                // }
                // if (mCollator.compare(startingLetter, targetLetter) < 0) {
                if (diff < 0) {
                    start = pos + 1;
                    if (start >= count) {
                        pos = count;
                        break;
                    }
                } else {
                    end = pos;
                }
            } else {
                // They're the same, but that doesn't mean it's the start
                if (start == pos) {
                    // This is it
                    break;
                } else {
                    // Need to go further lower to find the starting row
                    end = pos;
                }
            }
            pos = (start + end) / 2;
        }
        alphaMap.put(key, pos);
        cursor.moveToPosition(savedCursorPos);
        return pos;
	}

	@Override
	public int getSectionForPosition(int position) {
		
		if(mDataCursor == null || mDataCursor.isClosed()) {
			return 0;
		}
		
        int savedCursorPos = mDataCursor.getPosition();
        mDataCursor.moveToPosition(position);
        
//        String curName = mDataCursor.getString(mColumnIndex);
        String curName = null;
        String contactId = mDataCursor.getString(mColumnIndex);
        if(mContactsMap == null || mContactsMap.size() == 0){
        	return 0;
        } else {
        	curName = mContactsMap.get(contactId).getPinyin();
        }
        
        mDataCursor.moveToPosition(savedCursorPos);
        // Linear search, as there are only a few items in the section index
        // Could speed this up later if it actually gets used.
        for (int i = 0; i < mAlphabetLength; i++) {
            char letter = mAlphabet.charAt(i);
            String targetLetter = Character.toString(letter);
            if (compare(curName, targetLetter) == 0) {
                return i;
            }
        }
        return 0; // Don't recognize the letter - falls under zero'th section
	}
	
	@Override
	public void onChanged() {
		super.onChanged();
		mAlphaMap.clear();
	}
	
	@Override
	public void onInvalidated() {
		super.onInvalidated();
		mAlphaMap.clear();
	}
}
